package com.yunji.coupon.engine;

import cn.hutool.core.lang.Pair;
import com.yunji.common.constant.SystemConstant;
import com.yunji.common.model.dto.LoginInfo;
import com.yunji.common.utils.ConvertDataUtil;
import com.yunji.common.utils.DecimalUtils;
import com.yunji.common.utils.TimeUtils;
import com.yunji.coupon.bo.CouponBO;
import com.yunji.coupon.bo.CouponReceiveTimesBO;
import com.yunji.coupon.doamin.CouponDO;
import com.yunji.coupon.doamin.CouponSiteDO;
import com.yunji.coupon.doamin.CouponTmplDO;
import com.yunji.coupon.enums.*;
import com.yunji.coupon.service.CouponService;
import com.yunji.coupon.dto.OrderAmtDTO;
import com.yunji.system.context.SystemContext;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.Date;
import java.util.Optional;
import java.util.Set;

/**
 * @author : peter-zhu
 * @date : 2024/10/26 13:08
 * @description : 优惠券规则引擎
 **/
@Slf4j
@Component
public class CouponRuleEngine {

    @Resource
    private CouponService couponService;

    private static final String NOT_RECEIVE_COUNT_REASON = "领取次数上限";

    private static final String NOT_RECEIVE_NEW_REGISTER_REASON = "新人注册超过5天";

    private static final String NOT_USED_SITE_REASON = "当前网点不可用";

    private static final String NOT_USED_VALID = "当前用券时间未开始";

    private static final BigDecimal MIN_PAYABLE_AMT = new BigDecimal("0.01");

    // 打折的值需要除以10
    private static final BigDecimal DISCOUNT_CAL_AMT = new BigDecimal("10");
    /**
     * @param : domain
     * @return : com.yunji.coupon.bo.CouponBO
     * @author : peter-zhu
     * @date : 2024/10/28 9:19
     * @description : 根据规则获取所有可领取的优惠券
     */
    //FIXME 这里不是批量会导致性能差，后期期望改为批量处理 也就是所有的查询dao的方式改为批量查询 并根据map key匹配数据处理 然后传个数组
    public CouponBO getAvailableCoupon(CouponTmplDO domain) {
        if (domain == null)
            return null;
        LoginInfo user = SystemContext.getUser();
        Date createTime = Optional.ofNullable(user).map(LoginInfo::getCreateTime).orElseThrow(() -> new RuntimeException("用户没有注册时间"));
        int days = TimeUtils.differentDaysByMillisecond(createTime, new Date());
        Integer receiveRuleType = domain.getReceiveRuleType();
        boolean available;
        String reason = "";
        if (!ReceiveRuleTypeEnum.NONE.getCode().equals(domain.getReceiveRuleType())) {
            boolean day = ReceiveRuleTypeEnum.EVERY_TO_DAY.getCode().equals(receiveRuleType);
            Integer receiveTimes = couponService.receiveTimes(new CouponReceiveTimesBO(user.getUserId(),
                    domain.getCouponTmplId(), day));
            Integer everyReceiveCount = domain.getEveryReceiveCount();
            available = everyReceiveCount > receiveTimes;
            if (!available)
                return new CouponBO(false, NOT_RECEIVE_COUNT_REASON);
        } else {
            available = true;
        }
        // 如果可用而且是新人注册 判断注册时间有没有小于等于5
        available = CouponTypeEnum.NEW_REGISTER.getCode().equals(domain.getCouponType()) ? days <= 5 : available;
        if (!available)
            return new CouponBO(false, NOT_RECEIVE_NEW_REGISTER_REASON);
        else {
            CouponBO couponBO = new CouponBO();
            CouponDO couponDO = new CouponDO();
            couponDO.setCouponTmplId(domain.getCouponTmplId());
            couponDO.setUserId(user.getUserId());
            couponDO.setNickName(user.getNickname());
            couponDO.setPhone(user.getPhone());
            couponDO.setCouponStatus(CouponStatusEnum.UN_USED.getCode());
            if (ValidTimeTypeEnum.REGULAR.getCode().equals(domain.getValidTimeType())) {
                couponDO.setValidPeriodS(domain.getValidPeriodS());
                couponDO.setValidPeriodE(domain.getValidPeriodE());
            } else {
                LocalDateTime localDateTime = LocalDateTime.now().plusDays(domain.getValidDays());
                couponDO.setValidPeriodS(new Date());
                couponDO.setValidPeriodE(TimeUtils.toDate(localDateTime));
            }
            couponBO.setDomain(couponDO);
            couponBO.setTmplDO(domain);
            couponBO.setReason(reason);
            couponBO.setAvailable(available);
            return couponBO;
        }
    }

    public static BigDecimal getCouponAmt(Integer discountType, BigDecimal orderSaleAmt, BigDecimal couponValue) {
        BigDecimal rs = DiscountTypeEnum.SUBTRACT.getCode().equals(discountType) ? couponValue :
                BigDecimal.ONE.subtract(couponValue.divide(DISCOUNT_CAL_AMT,
                        SystemConstant.CALCULATE_SCALE, SystemConstant.ROUNDING_MODE)).multiply(orderSaleAmt);
        return DecimalUtils.less(rs) ? BigDecimal.ZERO : rs;
    }

    public static BigDecimal calculatePayableAmt(BigDecimal discountPrice, BigDecimal CouponAmt) {
        BigDecimal subtract = discountPrice.subtract(CouponAmt);
        return DecimalUtils.more(subtract) ? subtract : MIN_PAYABLE_AMT;
    }

    public static boolean isValid(Date validPeriodS, Date validPeriodE) {
        long time = new Date().getTime();
        return validPeriodS.getTime() <= time && time <= validPeriodE.getTime();
    }

    public Pair<Boolean, String> ruleSitesFilter(CouponTmplDO couponTmplDO, Long siteId) {
        if (!CouponSiteScopeEnum.ALL.getCode().equals(couponTmplDO.getSiteScopeType())) {
            Set<Long> siteIds = ConvertDataUtil.getDataSet(() -> couponService.getAvailableSites(siteId, couponTmplDO.getCouponTmplId()), CouponSiteDO::getSiteId);
//            Map<Boolean, Set<Long>> siteScopeMap =
//                    availableSites.stream().collect(Collectors.partitioningBy(a -> CouponSiteScopeEnum.SCOPE.getCode().equals(a.getSiteScopeType())
//                            , Collectors.mapping(CouponSiteDO::getSiteId, Collectors.toSet())));
            // 指定网点权限
            if (couponTmplDO.getSiteScopeType().equals(CouponSiteScopeEnum.SCOPE.getCode())) {
                if (siteIds.contains(siteId))
                    return new Pair<>(true, null);
                else
                    return new Pair<>(false, NOT_USED_SITE_REASON);
            } else if (couponTmplDO.getSiteScopeType().equals(CouponSiteScopeEnum.EXCLUDE_SCOPE.getCode())) {
                if (siteIds.contains(siteId))
                    return new Pair<>(false, NOT_USED_SITE_REASON);
            }
//            Set<Long> scopeSites = siteScopeMap.get(true);
//            if (scopeSites.contains(siteId)) {
//                return new Pair<>(true, null);
//            }
//            // 排除网点权限
//            Set<Long> excludeSites = siteScopeMap.get(false);
//            if (excludeSites.contains(siteId)) {
//                return new Pair<>(false, NOT_USED_SITE_REASON);
//            }
//            return new Pair<>(false, NOT_USED_SITE_REASON);
        }
        return new Pair<>(true, null);
    }

    public Pair<Boolean, String> ruleUseValid(CouponDO couponDO) {
        Date now = new Date();
        if (couponDO.getValidPeriodS().getTime() > now.getTime()) {
            return new Pair<>(false, NOT_USED_VALID);
        }
        return new Pair<>(true, null);
    }

    public static void setAmtByCoupon(OrderAmtDTO optimalCouponVO) {
        optimalCouponVO.setPayableAmount(CouponRuleEngine.calculatePayableAmt(optimalCouponVO.getDiscountAmount(), optimalCouponVO.getCoupleAmount()));
        //optimalCouponVO.setSubtractSumAmount(optimalCouponVO.getSubtractSumAmount().add(optimalCouponVO.getCoupleAmount()));
    }
}
